/*
 * @version: 1.0
 * @Author: zhl
 * @Date: 2021-12-30 10:03:39
 * @LastEditors: zhl
 * @LastEditTime: 2022-02-08 13:35:57
 * @Description: 文件实现描述
 */
#include <sstream>
#include <math.h>
#include <float.h>

#include "ModbusParseTask.h"
#include "modbus_datastruct.h"
#include "LinkageManager.h"
#include "modbus.h"
#include "common.h"
#include "CLogger.h"
#include "CDBManager.h"

#define LINKAGE_TOPIC      "pemd/rule/linkage/pemd/v1/default"

ModbusParseTask::ModbusParseTask(std::shared_ptr<CConfigManager> cm, 
        std::shared_ptr<CMosquittoManager> mm, 
        TASK_PARAMS *task_params, 
        std::list<std::shared_ptr<COLLECT_DATA>> &data, 
        std::unordered_map<std::string, RESPONSE_DATA> &rep_data){
    c_cm = cm;
    c_mm = mm;
    if(NULL == task_params){
        c_task_params = NULL;
    } else {
        c_task_params = new TASK_PARAMS();
        *c_task_params = *task_params;
    }
    c_data.swap(data);
    c_response_data.swap(rep_data);
}

ModbusParseTask::~ModbusParseTask(){
    if(NULL != c_task_params){
        delete c_task_params;
    }
}

template<typename T>
std::string ModbusParseTask::get_real_value(std::shared_ptr<MODBUS_POINT> point, T value){
    if(nullptr == point){
        return "";
    }
    double v = 0.0f;
    char ret_buf[64] = {0};
    char str_format[32] = {0};
    if(point->formula.empty()){
        v = value * point->scale;
    } else {
        std::string formula = "echo \""+point->formula + "\"|bc";
        std::stringstream fstr;
        fstr << value;
        
        formula = string_replace(formula, "X", fstr.str());
        if(0 == exec_cmd((char *)formula.c_str(), ret_buf, sizeof(ret_buf))){
            v = atof(ret_buf);
        }
    }
    if(0 < point->decimal){
        snprintf(str_format, sizeof(str_format), "%%.%dlf", point->decimal);
        snprintf(ret_buf, sizeof(ret_buf), str_format, v);
    } else {
        long nscale = (long)point->scale;
        if(fabs(point->scale-nscale) < FLT_EPSILON) {
            if(DATA_TYPE_FLOAT == point->data_type){
                snprintf(ret_buf, sizeof(ret_buf), "%lf", v);
            }else if(DATA_TYPE_SHORT == point->data_type ||
                DATA_TYPE_INT == point->data_type ||
                DATA_TYPE_BIT == point->data_type || 
                DATA_TYPE_BITS == point->data_type){
                snprintf(ret_buf, sizeof(ret_buf), "%d", (T)v);
            } else {
                snprintf(ret_buf, sizeof(ret_buf), "%u", (T)v);
            }
        } else {
            snprintf(ret_buf, sizeof(ret_buf), "%lf", v);
        }
    }
    
    return ret_buf;
}
//
uint16_t ModbusParseTask::get_short_value_by_order(std::shared_ptr<MODBUS_POINT> point, uint16_t *data){
    if(0 == point->byte_order.compare("12")){
        return modbus_get_short_ab(data);
    } else if(0 == point->byte_order.compare("21")){
        return modbus_get_short_ba(data);
    } else {
        return modbus_get_short(data);
    }
}
uint32_t ModbusParseTask::get_int_value_by_order(std::shared_ptr<MODBUS_POINT> point, uint16_t *data){
    if(0 == point->byte_order.compare("1234")){
        return modbus_get_int_abcd(data);
    } else if(0 == point->byte_order.compare("3412")){
        return modbus_get_int_cdab(data);
    } else if(0 == point->byte_order.compare("4321")){
        return modbus_get_int_dcba(data);
    } else if(0 == point->byte_order.compare("2143")){
        return modbus_get_int_badc(data);
    } else {
        return modbus_get_int(data);
    }
}
float    ModbusParseTask::get_float_value_by_order(std::shared_ptr<MODBUS_POINT> point, uint16_t *data){
    if(0 == point->byte_order.compare("1234")){
        return modbus_get_float_abcd(data);
    } else if(0 == point->byte_order.compare("3412")){
        return modbus_get_float_cdab(data);
    } else if(0 == point->byte_order.compare("4321")){
        return modbus_get_float_dcba(data);
    } else if(0 == point->byte_order.compare("2143")){
        return modbus_get_int_badc(data);
    } else {
        return modbus_get_float(data);
    }
}
//
int ModbusParseTask::get_point_value(std::shared_ptr<MODBUS_POINT> point, uint16_t *data, std::string &result){
    if((nullptr == point) || (nullptr == data)){
        return -1;
    }
    switch(point->data_type){
        case DATA_TYPE_SHORT:{
            uint16_t v = get_short_value_by_order(point, data);
            if(0 < point->bit_size){
                v = (~(0xFFFF << point->bit_size)) & (v >> point->bit_pos);
            }
            result = get_real_value<short>(point, (short)v);
        }
        break;
        case DATA_TYPE_UNSIGNED_SHORT:{
            uint16_t v = get_short_value_by_order(point, data);
            if(0 < point->bit_size){
                v = (~(0xFFFF << point->bit_size)) & (v >> point->bit_pos);
            }
            result = get_real_value<uint16_t>(point, v);
        }
        break;
        case DATA_TYPE_INT:{
            uint32_t v = (uint32_t)get_int_value_by_order(point, data);
            if(0 < point->bit_size){
                v = (~(0xFFFFFFFF << point->bit_size)) & (v >> point->bit_pos);
            }
            result = get_real_value<int>(point, (int)v);
        }
        break;
        case DATA_TYPE_UNSIGNED_INT:{
            uint32_t v = (uint32_t)get_int_value_by_order(point, data);
            if(0 < point->bit_size){
                v = (~(0xFFFFFFFF << point->bit_size)) & (v >> point->bit_pos);
            }
            result = get_real_value<uint32_t>(point, v);
        }
        break;
        case DATA_TYPE_FLOAT:{
            result = get_real_value<float>(point, get_float_value_by_order(point, data));
        }
        break;
        case DATA_TYPE_BIT:{
            uint16_t v = get_short_value_by_order(point, data);
            if(0 < point->bit_size){
                v = (~(0xFFFF << point->bit_size)) & (v >> point->bit_pos);
            }
            result = get_real_value<uint16_t>(point, v);
        }
        break;
        default:
        break;
    }
    return 0;
}
int ModbusParseTask::save_alert(ALERT_DATA &alert){
    return CDBManager::getInstance()->insert_alert_data(alert);
}
//
int ModbusParseTask::parse_alert(std::shared_ptr<MODBUS_POINT> point, int64_t dev_id, std::string value){
    if(nullptr == point){
        return -1;
    }
    if(!point->is_alarm){
        return -1;
    }
    //
    float v = std::stod(value);
    std::string alert_desc = "";
    if(v < point->lower_limit){
        alert_desc = point->lower_desc;
    } else if (v > point->upper_limit){
        alert_desc = point->upper_desc;
    }
    if((v < point->lower_limit) || (v > point->upper_limit)){
        //
        auto dev_alerts = c_cm->get_dev_alerts()->find(dev_id);
        if(dev_alerts == c_cm->get_dev_alerts()->end()){
            ALERT_DATA alert;
            alert.dev_id = dev_id;
            alert.alert_count = 1;
            alert.count = 1;
            alert.point_id = point->id;
            alert.report_time = get_timestamp();
            alert.state = 0;
            alert.restore_time = 0;
            alert.alert_desc = alert_desc;
            alert.value = value;
            alert.id = CDBManager::getInstance()->insert_alert_data(alert);
            std::unordered_map<int64_t, ALERT_DATA> alerts;
            alerts.insert(std::make_pair(point->id, alert));
            c_cm->get_dev_alerts()->insert(std::make_pair(dev_id, alerts));
        } else {
            auto point_alert = dev_alerts->second.find(point->id);
            if(point_alert == dev_alerts->second.end()){
                ALERT_DATA alert;
                alert.dev_id = dev_id;
                alert.alert_count = 1;
                alert.count = 1;
                alert.point_id = point->id;
                alert.report_time = get_timestamp();
                alert.state = 0;
                alert.restore_time = 0;
                alert.value = value;
                alert.alert_desc = alert_desc;
                alert.id = CDBManager::getInstance()->insert_alert_data(alert);
                dev_alerts->second.insert(std::make_pair(point->id, alert));
                
            }else{
                auto edge_dev = c_cm->get_edge_device();
                if(nullptr == edge_dev){
                    return -1;
                }
                point_alert->second.report_time = get_timestamp();
                point_alert->second.value = value;
                point_alert->second.alert_desc = alert_desc;
                if(point_alert->second.alert_count < point->threshold_count){
                    point_alert->second.alert_count++;
                    CDBManager::getInstance()->replace_alert_data(point_alert->second);
                }else{
                    point_alert->second.state = 1;
                    CReportManager::getInstance().report_alert(point_alert->second);
                    CDBManager::getInstance()->replace_alert_data(point_alert->second);
                    dev_alerts->second.erase(point->id);
                }
            }
        }
        //保存数据
    }else{
        auto dev_alerts = c_cm->get_dev_alerts()->find(dev_id);
        if(dev_alerts != c_cm->get_dev_alerts()->end()){
            auto point_alert = dev_alerts->second.find(point->id);
            if(point_alert != dev_alerts->second.end()){
                if(0 < point_alert->second.alert_count){
                    point_alert->second.alert_count--;
                }else{
                    point_alert->second.restore_time = get_timestamp();
                    point_alert->second.state = 2;
                    point_alert->second.alert_desc = alert_desc;
                    //保存报警数据
                    CDBManager::getInstance()->replace_alert_data(point_alert->second);
                    dev_alerts->second.erase(point->id);
                }
            }
            if(1 > dev_alerts->second.size()){
                c_cm->get_dev_alerts()->erase(dev_id);
            }
        }
    }
    
   return 0;
}
int ModbusParseTask::is_linkage(std::string op, std::string value1, std::string value2){
    
    double v1 = std::stod(value1);
    double v2 = std::stod(value2);
    if(0 == op.compare(">")){
        if(v1 > v2){
            return 1;
        }
    } else if(0 == op.compare(">=")){
        if(v1 >= v2){
            return 1;
        }
    } else if(0 == op.compare("<")){
        if(v1 < v2){
            return 1;
        }
    } else if(0 == op.compare("<=")){
        if(v1 <= v2){
            return 1;
        }
    } else if(0 == op.compare("!=")){
        if(v1 != v2){
            return 1;
        }
    } else if(0 == op.compare("=")){
        if(v1 == v2){
            return 1;
        }
    }
    return 0;
}
//
int ModbusParseTask::parse_linkage(std::shared_ptr<MODBUS_POINT> point, int64_t dev_id, std::string value){
    if(nullptr == point){
        return -1;
    }
    auto linkage_rule_ids = c_cm->get_linkage_point_rules(dev_id, point->id);
    if(nullptr == linkage_rule_ids){
        return -1;
    }
    for(auto linkage_rule_id = linkage_rule_ids->begin(); linkage_rule_id != linkage_rule_ids->end(); linkage_rule_id++){
        auto linkage_rule = c_cm->get_linkage_rule(*linkage_rule_id);
        if(nullptr == linkage_rule){
            continue;
        }
        if((true == linkage_rule->is_use) && 1 == is_linkage(linkage_rule->op, value, linkage_rule->condition_value)){
            char buf[128] = {0};
            snprintf(buf, sizeof(buf), "{\"dev_id\":%lld,\"cmd_id\":%lld}", linkage_rule->linkage_dev_id, linkage_rule->command_id);
            c_mm->publish(LINKAGE_TOPIC, strlen(buf), (void *)buf);
        }
    }
    return 0;
}

int ModbusParseTask::parse_multi_linkage(std::shared_ptr<MODBUS_POINT> point, int64_t dev_id, std::string value){
    if(nullptr == point){
        return -1;
    }
    return LinkageManager::getInstance().parse_linkage(c_cm, c_mm, dev_id, point->id, value);
}

int ModbusParseTask::parse_register(std::shared_ptr<COLLECT_DATA> data_ptr, std::unordered_map<int64_t, std::list<int64_t>> &reg_points){
 //
    int reg_start_addr = data_ptr->start_addr;
    int reg_end_addr = data_ptr->start_addr + data_ptr->reg_count;
    int offset = 0;
    while(reg_start_addr < reg_end_addr){
        auto points = reg_points.find(reg_start_addr);
        if(points == reg_points.end()){
            reg_start_addr += 1;
            continue;
        }
        for(auto point_id = points->second.begin(); point_id != points->second.end(); point_id++){
            auto point = c_cm->get_modbus_point(*point_id);
            if(nullptr == point){
                continue;
            }
            offset = point->reg_count;
            if(point->func_code != data_ptr->func_code){
                CLogger::getInstance()->LOG_W("请求与解析功能码不匹配(dev:%lld, point:%lld)", data_ptr->dev_id, point->id);
                continue;
            }
            std::string value = "";
            
            uint16_t d = *((uint16_t *)(data_ptr->data)+(reg_start_addr - data_ptr->start_addr));
            get_point_value(point, ((uint16_t *)(data_ptr->data)+(reg_start_addr - data_ptr->start_addr)), value);
            // CLogger::getInstance()->log_w("point_id: %lld, data_type:%d, name:%s, src_v:%04X, value:%s", point->id, point->data_type, point->name.c_str(), d, value.c_str());
            //联动
            update_linkage_action_last_value(point, data_ptr->dev_id, value);
            parse_multi_linkage(point, data_ptr->dev_id, value);
            parse_alert(point, data_ptr->dev_id, value);
            save_point_value(point, value);

        }
        reg_start_addr += offset;
        offset = 0;
    }   
    return 0; 
}
//
int ModbusParseTask::save_point_value(std::shared_ptr<MODBUS_POINT> point, std::string value){
    auto pv = c_response_data.find(point->name);
    RESPONSE_DATA data;
    data.point_id = point->id;
    data.data_type = point->data_type;
    data.name = point->name;
    data.value = value;
    data.code = 0;
    data.msg = "successed";
    // CLogger::getInstance()->log_w("point_id: %lld, name:%s, value:%s", point->id, point->name.c_str(), value.c_str());
    if(pv == c_response_data.end()){
        c_response_data.insert(std::make_pair(point->name, data));
    }else{
        c_response_data[point->name] = data;
    }
    return 0;
}
//
int ModbusParseTask::parse_coil(std::shared_ptr<COLLECT_DATA> data_ptr, std::unordered_map<int64_t, std::list<int64_t>> &reg_points){
    int reg_start_addr = data_ptr->start_addr;
    int reg_end_addr = data_ptr->start_addr + data_ptr->reg_count;
    int offset = 0;
    while(reg_start_addr < reg_end_addr){
        auto points = reg_points.find(reg_start_addr);
        if(points == reg_points.end()){
            return -1;
        }
        for(auto point_id = points->second.begin(); point_id != points->second.end(); point_id++){
            auto point = c_cm->get_modbus_point(*point_id);
            if((nullptr == point) || (point->func_code != data_ptr->func_code)){
                continue;
            }
            offset = point->bit_pos;
            uint8_t value = *(uint8_t *)(data_ptr->data+(reg_start_addr - data_ptr->start_addr) + offset);
            //联动
            update_linkage_action_last_value(point, data_ptr->dev_id, std::to_string(value));
            parse_multi_linkage(point, data_ptr->dev_id, std::to_string(value));
            parse_alert(point, data_ptr->dev_id, std::to_string(value));
            save_point_value(point, std::to_string(value));
        }
        reg_start_addr += 1;
        offset = 0;
    }
    return 0;
}
int ModbusParseTask::parse(std::shared_ptr<COLLECT_DATA> data_ptr){
    if(nullptr == data_ptr){
        return -1;
    }
    auto rules = c_cm->get_modbus_rule_points();
    if(nullptr == rules){
        return -1;
    }
    auto rule = rules->find(data_ptr->protocol_id);
    if(rule == rules->end()){
        return -1;
    }
    if((0x01 == data_ptr->func_code) || (0x02 == data_ptr->func_code)){
        return parse_coil(data_ptr, rule->second);
    } else if((0x03 == data_ptr->func_code) || (0x04 == data_ptr->func_code)){
        return parse_register(data_ptr, rule->second);
    }
    return 0;
}

void ModbusParseTask::run(){
    
    int64_t dev_id = 0; 
    while(!c_data.empty()){
        auto data = c_data.front();
        //解析方法
        dev_id = data->dev_id;
        parse(data);
        if(NULL != data->data){
            free(data->data);
            data->data = NULL;
        }        
        c_data.pop_front();
    }
    //上报数据
    if(1 > dev_id){
        CLogger::getInstance()->log_w("不存在该设备:%lld", dev_id);
    } else {
        if((NULL != c_task_params) && (c_task_params->type == TASK_TYPE_GET_CMD_RULE)){
            CReportManager::getInstance().report_get_cmd_data(c_task_params, "CMD_GET_COMMAND_RULE", c_mm, c_response_data);
        } else {
            CReportManager::getInstance().push_data(dev_id, c_response_data);
        }
    }
}

/// @brief 更新联动动作当前值
/// @param point 
/// @param dev_id 
/// @param value 
/// @return 
int ModbusParseTask::update_linkage_action_last_value(std::shared_ptr<MODBUS_POINT> point, int64_t dev_id, std::string value){
    if((nullptr == point) || (nullptr == c_cm)){
        return -1;
    }
    auto rules_relate_cmds = c_cm->get_modbus_rule_commands();
    if(nullptr == rules_relate_cmds){
        return -1;
    }
    auto rule_relate_cmds = rules_relate_cmds->find(point->protocol_id);
    if(rule_relate_cmds == rules_relate_cmds->end()){
        return -1;
    }
    for(auto rule_relate_cmd = rule_relate_cmds->second.begin(); rule_relate_cmd != rule_relate_cmds->second.end(); rule_relate_cmd++){
        auto cmd = c_cm->get_modbus_command(rule_relate_cmd->first);
        if(nullptr != cmd){
            if(point->reg_addr == cmd->reg_addr){
                if(LinkageManager::getInstance().c_last_value_mutex.try_lock()){
                    LinkageManager::getInstance().insert_action_last_value(dev_id, cmd->id, value);
                    LinkageManager::getInstance().c_last_value_mutex.unlock();
                }
                return 0;
            }
        }
    }
    return -1;
}